﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Text.RegularExpressions;
using System.ComponentModel;
using Newtonsoft.Json.Linq;
using System.Text;

namespace ShortcutToolbox.Core
{
    public class UpdateChecker
    {
        public virtual string CurrentVersion { get; set; }
        public virtual string ReleasePage { get; set; }
        public virtual string ApiUrl { get; set; }
        public virtual string UserAgent { get; set; } = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36";
        public GetReleaseListHandler GetReleaseList { get; set; }
        public event EventHandler<CheckUpdateEventArgs> CheckUpdateCompleted;
        public event EventHandler<DownloadEventArgs> DownloadCompleted;

        public UpdateChecker()
        {
            GetReleaseList = new GetReleaseListHandler(_GetReleaseList);
        }

        #region Check Update

        public virtual void CheckUpdate(int delay = 0, object userState = null)
        {
            CheckUpdateState state = new CheckUpdateState
            {
                apiUrl = ApiUrl,
                currentVersion = CurrentVersion,
                userState = userState
            };
            CheckUpdate(state, delay);
        }

        protected virtual void CheckUpdate(CheckUpdateState state, int delay = 0)
        {
            if (delay > 0)
            {
                CheckUpdateTimer timer = new CheckUpdateTimer(delay);
                timer.State = state;
                timer.AutoReset = false;
                timer.Elapsed += Timer_Elapsed;
                timer.Enabled = true;
            }
            else
            {
                CheckUpdate(state);
            }
        }

        protected virtual void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            CheckUpdateTimer timer = (CheckUpdateTimer)sender;
            CheckUpdateState state = timer.State;
            timer.Elapsed -= Timer_Elapsed;
            timer.Enabled = false;
            timer.Dispose();
            CheckUpdate(state);
        }

        protected virtual void CheckUpdate(CheckUpdateState state)
        {
            try
            {
                Logging.Debug($"Checking updates... - {state.apiUrl}");
                if (string.IsNullOrEmpty(state.currentVersion))
                {
                    CheckUpdateCompleted?.Invoke(this, new CheckUpdateEventArgs()
                    {
                        ApiUrl = state.apiUrl,
                        CurrentVersion = state.currentVersion,
                        UserState = state.userState
                    });
                }
                else
                {
                    WebClient http = CreateWebClient();
                    http.Encoding = Encoding.UTF8;
                    http.DownloadStringCompleted += http_DownloadStringCompleted;
                    http.DownloadStringAsync(new Uri(state.apiUrl), state);
                }
            }
            catch (Exception ex)
            {
                Logging.LogUsefulException(ex);
            }
        }

        protected virtual void http_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            try
            {
                CheckUpdateState state = (CheckUpdateState)e.UserState;

                string response = e.Result;


                CheckUpdateEventArgs args = new CheckUpdateEventArgs();
                args.ApiUrl = state.apiUrl;
                args.CurrentVersion = state.currentVersion;
                args.UserState = state.userState;
                args.ReleaseList.AddRange(GetReleaseList.Invoke(this, response));
                CheckUpdateCompleted?.Invoke(this, args);
            }
            catch (Exception ex)
            {
                Logging.LogUsefulException(ex);
            }
        }

        protected virtual IEnumerable<ReleaseInfo> _GetReleaseList(object sender, string response)
        {
            List<ReleaseInfo> list = new List<ReleaseInfo>();
            JObject json = JObject.Parse(response);
            if (json == null)
                return list;
            bool prerelease = (bool)json["prerelease"];
            string name;
            string version;
            string browser_download_url;

            foreach (JObject asset in (JArray)json["assets"])
            {
                browser_download_url = (string)asset["browser_download_url"];
                Match match = Regex.Match(browser_download_url, @"^.*?/attach_files/download\?.*?%26attname%3D(\S*?)$", RegexOptions.IgnoreCase);
                if (match.Success && match.Groups.Count == 2)
                    name = match.Groups[1].Value;
                else
                    name = string.Empty;
                match = Regex.Match(name, @"^.*?-([\d\.]+)\.\S+$", RegexOptions.IgnoreCase);
                if (match.Success && match.Groups.Count == 2)
                    version = match.Groups[1].Value;
                else
                    version = string.Empty;
                ReleaseInfo ass = new ReleaseInfo
                {
                    prerelease = prerelease,
                    name = name,
                    version = version,
                    browser_download_url = browser_download_url,
                    url = browser_download_url,
                    size = 0,
                };
                if (IsNewVersion(ass))
                    list.Add(ass);
            }

            return list;
        }

        protected virtual bool IsNewVersion(ReleaseInfo release)
        {
            if (string.IsNullOrEmpty(release.version))
                return false;
            return CompareVersion(release.version, CurrentVersion) > 0;
        }

        public static int CompareVersion(string l, string r)
        {
            var ls = l.Split('.');
            var rs = r.Split('.');
            for (int i = 0; i < Math.Max(ls.Length, rs.Length); i++)
            {
                int lp = (i < ls.Length) ? int.Parse(ls[i]) : 0;
                int rp = (i < rs.Length) ? int.Parse(rs[i]) : 0;
                if (lp != rp)
                {
                    return lp - rp;
                }
            }
            return 0;
        }

        #endregion

        #region Download File

        public virtual void Download(ReleaseInfo release, string saveTo = null, object userState = null)
        {
            DownloadState state = new DownloadState
            {
                release = release,
                saveTo = string.IsNullOrEmpty(saveTo) ? Utils.GetAppDataFilePath(release.name) : saveTo,
                percent = 0,
                userState = userState,
            };
            Download(state);
        }

        protected virtual void Download(DownloadState state)
        {
            try
            {
                WebClient http = CreateWebClient();
                http.DownloadFileCompleted += Http_DownloadFileCompleted;
                http.DownloadProgressChanged += Http_DownloadProgressChanged;
                state.percent = 0;
                http.DownloadFileAsync(new Uri(state.release.browser_download_url), state.saveTo, state);
                Logging.Debug($"Downloading {state.release.browser_download_url}...");
            }
            catch (Exception ex)
            {
                Logging.LogUsefulException(ex);
            }
        }

        protected virtual void Http_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            try
            {
                DownloadState state = (DownloadState)e.UserState;
                if (state.percent != e.ProgressPercentage)
                {
                    state.percent = e.ProgressPercentage;
                    Logging.Debug($"{e.ProgressPercentage}%  {e.BytesReceived}/{e.TotalBytesToReceive} Bytes");
                }
            }
            catch (Exception ex)
            {
                Logging.LogUsefulException(ex);
            }
        }

        protected virtual void Http_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
        {
            try
            {
                DownloadState state = (DownloadState)e.UserState;
                DownloadEventArgs args = new DownloadEventArgs();
                args.Error = e.Error;
                args.SaveTo = state.saveTo;
                args.Release = state.release;
                args.UserState = state.userState;
                if (e.Error != null)
                    Logging.LogUsefulException(e.Error);
                DownloadCompleted?.Invoke(this, args);
            }
            catch (Exception ex)
            {
                Logging.LogUsefulException(ex);
            }
        }

        #endregion

        protected virtual WebClient CreateWebClient()
        {
            WebClient http = new WebClient();
            http.Headers.Add("User-Agent", UserAgent);
            http.Proxy = WebRequest.GetSystemWebProxy();
            return http;
        }

        protected virtual void SortByVersions(List<ReleaseInfo> asserts)
        {
            asserts.Sort(new ReleaseComparer());
        }

        public class CheckUpdateTimer : System.Timers.Timer
        {
            public CheckUpdateState State { get; set; }

            public CheckUpdateTimer(int p) : base(p)
            {
            }
        }

        public class ReleaseComparer : IComparer<ReleaseInfo>
        {
            // Calls CaseInsensitiveComparer.Compare with the parameters reversed. 
            public int Compare(ReleaseInfo x, ReleaseInfo y)
            {
                return UpdateChecker.CompareVersion(x.version, y.version);
            }
        }

        public class CheckUpdateState
        {
            public string apiUrl;
            public string currentVersion;
            public object userState;
        }

        public class DownloadState
        {
            public ReleaseInfo release;
            public string saveTo;
            public int percent;
            public object userState;
        }

        public delegate IEnumerable<ReleaseInfo> GetReleaseListHandler(object sender, string response);
    }
}
